package com.csht.facelib.manager;

import android.graphics.Rect;
import android.hardware.Camera;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;

import com.csht.facelib.api.FaceSdk;
import com.csht.facelib.db.DbManager;
import com.csht.facelib.db.User;
import com.csht.facelib.utils.Constants;
import com.csht.facelib.utils.SpUtils;
import com.csht.facelib.utils.ToastUtils;
import com.csht.facelib.utils.UnzipAssets;
import com.cshtface.sdk.AuthStatusListener;
import com.cshtface.sdk.FaceTools;
import com.cshtface.sdk.bean.msg;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import static com.cshtface.common.Constants.IMG_ANGLE_0;
import static com.cshtface.common.Constants.IMG_FORMAT_BINARY;
import static com.cshtface.common.Constants.IMG_MIRROR_NONE;
import static com.cshtface.common.Constants.OP_ALIGN;
import static com.cshtface.common.Constants.OP_DET;
import static com.cshtface.common.Constants.OP_QUALITY_BASE;

/**
 * Created by Administrator on 2019-10-15.
 */

public class CshtCompareUtil {

    private static CshtCompareUtil instance = null;
    private FaceTools faceTools;//人脸比对，提取特征值工具
    private boolean isInit;
    private ExecutorService ess;
    private ExecutorService es = Executors.newSingleThreadExecutor();
    private Future future;
    private ArrayList<byte[]> features = new ArrayList<>();
    private ArrayList<User> users = new ArrayList<>();

    private CshtCompareUtil() {
        ess = Executors.newCachedThreadPool();
    }

    public interface CallBack{
        void onInitStart();
        void onInitSuccess();
        void onInitFail();
        void onInitAuthFail();
    }

    public void init(final CallBack cb){
        if (isInit){
            cb.onInitSuccess();
        }else {
            Executors.newCachedThreadPool().execute(new Runnable() {
                @Override
                public void run() {
                    if (TextUtils.isEmpty(SpUtils.getFaceSDKAuthCode())){
                        cb.onInitAuthFail();
                    } else {
                        faceToolInit(cb);
                    }
                }
            });
        }
    }

    public boolean isInit(){
        return isInit;
    }

    private boolean isInitAuth;
    private void faceToolInit(final CallBack cb) {
        if (faceTools == null) {
            cb.onInitStart();
            String path = Constants.CSHT_MODEL_FILE_DIRECTORY;
            File file = new File(path);
            if (!file.exists() || (file.listFiles() != null && file.listFiles().length < 7)) {
                try {
                    UnzipAssets zip = new UnzipAssets();
                    zip.unZip(FaceSdk.getInstance().getContext(), "CSHTModels.zip",
                            Environment.getExternalStorageDirectory().getPath());
                } catch (IOException e) {
                    Log.e("zxb", "run->  faceTool  ：解压模型文件失败");
                    e.printStackTrace();
                }
            }
            faceTools = new FaceTools(SpUtils.getFaceSDKAuthCode(), new AuthStatusListener() {
                @Override
                public void onAuthStatus(int i) {
                    Log.e("zxb","授权状态："+i);
                    //30022:未授权/授权失败
                    //30024:授权码到期
                    //30023:授权码不存在
                    if (i == 30022){
                        ToastUtils.showToast("授权失败");
                    } else if (i == 30023){
                        ToastUtils.showToast("授权码不存在");
                    } else if (i == 30024){
                        ToastUtils.showToast("授权码到期");
                    }
                    isInitAuth = i==1;
                }
            });
            faceTools.setModelPath(path);
            boolean isInitHandle = faceTools.initAllTools(30, 700);
            boolean isInitSuccess = faceTools.initFeatureHandle();
            Log.e("zxb", "run->  初始化faceTool 检测featureHandler状态:"+ isInitHandle+"="+ isInitSuccess +"=" + isInitAuth);
            if (isInitHandle && isInitSuccess && isInitAuth) {
                Log.e("zxb", "run->  faceTool  ：初始化成功");
                isInit = true;
                cb.onInitSuccess();
            } else {
                isInit = false;
                cb.onInitFail();
            }
        }
    }

    private static int FACE_UNIT = 30;

    public synchronized static CshtCompareUtil getInstance() {
        if (instance == null) {
            instance = new CshtCompareUtil();
        }
        return instance;
    }

    /**
     * 提取人脸特征值
     *
     * @param idCardFaces 检测出的人脸数据
     * @param isCompared  是否比对对象
     * @return 人脸特征值
     */
    public byte[] getFaceDataFeature(msg[] idCardFaces, boolean isCompared) {
        if (faceTools != null && idCardFaces != null) {
            return faceTools.getFeature(idCardFaces[0], isCompared);
        }
        return null;
    }

    /**
     * 提取人脸特征值
     *
     * @param isCompared  是否比对对象
     * @return 人脸特征值
     */
    public byte[] getFaceDataFeature(byte[] nowfaceBmp, int width, int height, int angle, boolean isCompared) {
        if (faceTools != null) {
             msg[] idCardFaces = getCheckFaceMSG(nowfaceBmp,width,height,angle);
             if (idCardFaces != null)
                 return faceTools.getFeature(idCardFaces[0], isCompared);
        }
        return null;
    }


    /**
     * 单人比对
     *
     * @param nowFacefeature    现场特征值
     * @param idCardFacefeature 被比对对象
     * @return 分数集合
     */
    public float[] getNNCompareScore(byte[] nowFacefeature, byte[] idCardFacefeature) {
        if (faceTools != null && nowFacefeature != null && idCardFacefeature != null) {
            return faceTools.compare(nowFacefeature, idCardFacefeature);
        }
        return null;
    }

    /**
     * 获取人脸的参数()
     *
     * @param idCardFaceData
     * @param rect
     * @return
     */
    public msg[] getCheckFaceMSG(byte[] idCardFaceData, Rect rect) {//一般针对与身份证
        if (faceTools != null && idCardFaceData != null) {
            return faceTools.checkImage(idCardFaceData, OP_ALIGN | OP_QUALITY_BASE | OP_DET, IMG_FORMAT_BINARY,
                    rect.width(), rect.height(), IMG_ANGLE_0, IMG_MIRROR_NONE);
        }
        return null;
    }

    public msg[] getCheckFaceMSG(byte[] nowfaceBmp, Rect rect, int angle) {//一般针对于现场照
        if (faceTools != null && nowfaceBmp != null) {
            return faceTools.checkImage(nowfaceBmp, OP_ALIGN | OP_QUALITY_BASE | OP_DET, IMG_FORMAT_BINARY,
                    rect.width(), rect.height(), angle, IMG_MIRROR_NONE);
        }
        return null;
    }

    public msg[] getCheckFaceMSG(byte[] nowfaceBmp, int width, int height, int angle) {//一般针对于现场照
        if (faceTools != null && nowfaceBmp != null) {
            return faceTools.checkImage(nowfaceBmp, OP_ALIGN | OP_QUALITY_BASE | OP_DET, IMG_FORMAT_BINARY,
                    width, height, angle, IMG_MIRROR_NONE);
        }
        return null;
    }

    public msg[] getCheckFaceMSG(byte[] idCardFaceData) {//一般针对于身份证
        if (faceTools != null && idCardFaceData != null) {
            return faceTools.checkImage(idCardFaceData, OP_ALIGN | OP_QUALITY_BASE | OP_DET, IMG_FORMAT_BINARY,
                    0, 0, IMG_ANGLE_0, IMG_MIRROR_NONE);
        }
        return null;
    }

    public byte[] getStrictImgByte(msg face) {
        if (faceTools != null){
            if (face.faceScore >= Constants.faceScore && face.clearness >= Constants.clearness && face.pointScore >= Constants.pointScore) {
                return faceTools.getFeature(face, true);
            }
        }
        return null;
    }

    public byte[] getFeatureFromData(byte[] imgData, boolean isCompared){
        try {
            if (faceTools != null){
                msg[] data = getCheckFaceMSG(imgData);
                if (null != data && null != data[0])
                    return faceTools.getFeature(data[0], isCompared);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    public void faceToolsDestory() {
        if (faceTools != null) {
            faceTools.destroy();
        }
    }

    /**
     * 判断是否达到标准
     *
     * @param getResultMsg
     * @return
     */
    public static boolean isQualityFaceData(msg getResultMsg) {
//        Log.e("zxb", " faceScore  标准 " + Constants.faceScore + " 当前 " + getResultMsg.faceScore + "   " + (getResultMsg.faceScore > Constants.faceScore) +
//                " pointScore  标准 " + Constants.pointScore + " 当前 " + getResultMsg.pointScore + "   " + (getResultMsg.pointScore > Constants.pointScore) +
//                " clearness  标准 " + Constants.clearness + " 当前 " + getResultMsg.clearness + "   " + (getResultMsg.clearness > Constants.clearness) +
//                " roll  标准  5.0f  -4.0f 当前 " + getResultMsg.roll + "   " + (getResultMsg.roll < 10.0f || -10.0f < getResultMsg.roll) +
//                " pitch  标准 " + 26.0f + "   " + -20.0f + " 当前 " + getResultMsg.pitch + "   " + (getResultMsg.pitch < 26.0f && getResultMsg.pitch > -20.0f) +
//                "yaw 当前  " + getResultMsg.yaw
//        );
        boolean isFaceOk = getResultMsg.faceScore > Constants.faceScore && getResultMsg.pointScore > Constants.pointScore && getResultMsg.clearness > Constants.clearness;
        //                && (-4.0f < getResultMsg.roll && getResultMsg.roll < 5.0f) && (26.0f > getResultMsg.pitch && getResultMsg.pitch > -20.0f)) {

        return isFaceOk;
    }

    /**
     * 多人比对
     * @param nowFaceByte                现场特征值
     * @return 比对出的人员
     */

    public synchronized User multithreadingGroupCompare(final byte[] nowFaceByte) {
        try {
            final List<byte[]> nowFaceByteList = new ArrayList<>();
            nowFaceByteList.add(nowFaceByte);
            User user = null;
            final List<User> resultList = new ArrayList<>();
            final List<List<byte[]>> averageList = splitList(features, FACE_UNIT);
            final CountDownLatch countDownLatch = new CountDownLatch(averageList.size());
            for (int i = 0; i < averageList.size(); i++) {
                final int finalI = i;
                ess.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            if (nowFaceByteList.size() > 0) {
                                float[] groupCompareScores = faceTools.compareNM(nowFaceByteList, averageList.get(finalI));
                                if (groupCompareScores != null) {
                                    float[] groupCompareHighestResult = highestSort(groupCompareScores, finalI, FACE_UNIT);
                                    if (groupCompareHighestResult != null){
                                        int maxScore = (int) (groupCompareHighestResult[0] * 100);
                                        int index = (int) groupCompareHighestResult[1];
                                        if (index != -1) {
                                            User user = users.get(index);
                                            user.setScore(maxScore);
                                            resultList.add(user);
                                        }
                                    }
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        countDownLatch.countDown();
                    }
                });
            }
            countDownLatch.await(6000, TimeUnit.MILLISECONDS);
            if (resultList.size() > 0) {
                Collections.sort(resultList, new Comparator<User>() {
                    @Override
                    public int compare(User o1, User o2) {
                        return Integer.compare(o2.getScore(), o1.getScore());
                    }
                });
                user = resultList.get(0);
            }
            if (user != null) {
                Log.e("zxb", "小数量人员   相似人员： 比分  " + user.getScore() + " 姓名  " + user.getName());
            }
            return user;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private float[] highestSort(float[] pScores, int count, int unit) {
        if (pScores == null) {
            return null;
        }
        if (pScores.length == 1) {
            return new float[]{pScores[0], unit * count};
        }
        float index = -1;
        float max = 0;
        for (int i = 0; i < pScores.length; i++) {
            if (max < pScores[i]) {
                max = pScores[i];
                index = i;
            }
        }
        index = index + count * unit;
        return new float[]{max, index};
    }

    private <T> List<List<T>> splitList(List<T> list, int pageSize) {
        int listSize = list.size();                                                           //list的大小
        int page = (listSize + (pageSize - 1)) / pageSize;                      //页数
        List<List<T>> listArray = new ArrayList<List<T>>();              //创建list数组 ,用来保存分割后的list
        for (int i = 0; i < page; i++) {                                                         //按照数组大小遍历
            List<T> subList = new ArrayList<T>();                               //数组每一位放入一个分割后的list
            for (int j = 0; j < listSize; j++) {                                                 //遍历待分割的list
                int pageIndex = ((j + 1) + (pageSize - 1)) / pageSize;   //当前记录的页码(第几页)
                if (pageIndex == (i + 1)) {                                               //当前记录的页码等于要放入的页码时
                    subList.add(list.get(j));                                               //放入list中的元素到分割后的list(subList)
                }
                if ((j + 1) == ((j + 1) * pageSize)) {                               //当放满一页时退出当前循环
                    break;
                }
            }
            listArray.add(subList);                                                         //将分割后的list放入对应的数组的位中
        }
        return listArray;
    }

    /**
     * 数据库发现变化时候，重新把数据库中的人脸信息添加到内存中，id+feature
     */
    public void initDatabases() {

        if (future != null && !future.isDone()) {
            future.cancel(true);
        }

        future = es.submit(new Runnable() {
            @Override
            public void run() {
                List<User> list = DbManager.getInstance().queryAllUser();
                if (list != null && list.size() > 0) {
                    features.clear();
                    users.clear();
                    for (int i = 0; i < list.size(); i++) {
                        User user = list.get(i);
                        if (user.getFeature() != null) {
                            features.add(user.getFeature());
                            users.add(user);
                        }
                    }
                }
            }
        });
    }

    /**
     * 调整人脸框用来绘制
     *
     * @param ftRect FT人脸框
     * @return 调整后的需要被绘制到View上的rect
     */
    public Rect adjustRect(Rect ftRect,int width,int height,int orientation,int cameraId) {

        if (ftRect == null) {
            return null;
        }

        Rect rect = new Rect(ftRect);
        float horizontalRatio;
        float verticalRatio;
        if (orientation % 180 == 0) {
            horizontalRatio = (float) width / (float) width;
            verticalRatio = (float) height / (float) height;
        } else {
            horizontalRatio = (float) height / (float) width;
            verticalRatio = (float) width / (float) height;
        }
        rect.left *= horizontalRatio;
        rect.right *= horizontalRatio;
        rect.top *= verticalRatio;
        rect.bottom *= verticalRatio;

        Rect newRect = new Rect();
        switch (orientation) {
            case 0:
                if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    newRect.left = width - rect.right;
                    newRect.right = width - rect.left;
                } else {
                    newRect.left = rect.left;
                    newRect.right = rect.right;
                }
                newRect.top = rect.top;
                newRect.bottom = rect.bottom;
                break;
            case 90:
                newRect.right = width - rect.top;
                newRect.left = width - rect.bottom;
                if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    newRect.top = height - rect.right;
                    newRect.bottom = height - rect.left;
                } else {
                    newRect.top = rect.left;
                    newRect.bottom = rect.right;
                }
                break;
            case 180:
                newRect.top = height - rect.bottom;
                newRect.bottom = height - rect.top;
                if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    newRect.left = rect.left;
                    newRect.right = rect.right;
                } else {
                    newRect.left = width - rect.right;
                    newRect.right = width - rect.left;
                }
                break;
            case 270:
                newRect.left = rect.top;
                newRect.right = rect.bottom;
                if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    newRect.top = rect.left;
                    newRect.bottom = rect.right;
                } else {
                    newRect.top = height - rect.right;
                    newRect.bottom = height - rect.left;
                }
                break;
            default:
                break;
        }
        if (false ^ false) {
            int left = newRect.left;
            int right = newRect.right;
            newRect.left = width - right;
            newRect.right = width - left;
        }
        if (false) {
            int top = newRect.top;
            int bottom = newRect.bottom;
            newRect.top = height - bottom;
            newRect.bottom = height - top;
        }
        return newRect;
    }
}
